﻿#if UNITY_EDITOR

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace AI.Graph.Editor {


    [GraphEventOrder (1000)]
    public class SelectAndMoveNodeEvent : GraphEvent {

        private Node[] m_currentSelectedCanvasNodes;
        private Node m_currentSelectedNode;
        private readonly List<Node> m_selectedNodes = new List<Node> ();
        private Vector2 m_selectionStartPosition;
        private Vector2[] m_nodeDragStartPositions;
        private Vector2 m_dragDelta;
        private bool m_dragPerformed;
        private bool m_dragStarted;
        private bool m_selectionWithControlKey;
        private bool m_selectionWithShiftKey;
        private bool m_multiSelectionStarted;
        private Rect m_multiSelectionScreenRect;
        private Rect m_multiSelectionCanvasRect;


        public override void OnInitialize () {
            canvas.onBeforeCanvasGUI += OnDrawBeforeNodes;
            canvas.onAfterCanvasGUI += OnDrawAfterNodes;
        }


        void OnDrawBeforeNodes () {
            if (!m_multiSelectionStarted) {
                return;
            }

            EditorGUI.DrawSolidTexture (m_multiSelectionScreenRect, Styles.multiSelectionBackgroundColor.color);
        }


        void OnDrawAfterNodes () {
            if (!m_multiSelectionStarted) {
                return;
            }

            EditorGUI.DrawSolidTexture (m_multiSelectionScreenRect, Styles.multiSelectionOutlineColor.color, new BorderWidth (1));
        }


        public override void OnProcess (Event e) {
            // Hover, select and drag start can only start if the mouse is in the canvas and drag and multi selection is not started. 
            if (e.IsMouseInsideRect (canvas.unscaledRect) && !m_dragStarted && !m_multiSelectionStarted) {
                bool nodeSelected = false;
                bool nodeHovered = false;

                // if left mouse button is down, save the selection position,
                // all current selected nodes in the canvas and clear the current selected nodes.
                if (e.IsMouseDown (MouseButton.Left)) {
                    m_selectionStartPosition = canvas.mousePosition;
                    m_currentSelectedCanvasNodes = canvas.GetSelectedNodes ();
                    m_selectedNodes.Clear ();
                }

                for (int i = 0; i < canvas.nodes.Length; i++) {
                    Node node = canvas.nodes[i];

                    if (node.isHidden) {
                        continue;
                    }

                    if (!nodeHovered && node.rect.Contains (canvas.mousePosition)) {
                        // set the node under the mouse to the hover state.
                        // But only if the shift key is not clicked. Or the control key is clicked.
                        if (e.control || !e.shift) {
                            canvas.SetHoveredNode (node);
                            nodeHovered = true;
                        }

                        if (e.IsMouseDown (MouseButton.Left)) {
                            // check if a port is under the mouse.
                            ConnectionPort[] nodePorts = node.ports;
                            for (int j = 0; j < nodePorts.Length; j++) {
                                if (nodePorts[j].rect.Contains (canvas.mousePosition)) {
                                    // node drag cannot start.
                                    // select the current node and return.
                                    canvas.SetSelectedNode (node);
                                    return;
                                }
                            }

                            // a node is selected.
                            nodeSelected = true;
                            m_currentSelectedNode = node;

                            if (e.control) {
                                // if the control key is down add all current selected nodes in the canvas 
                                // and add the current selected node if its not in the current selected node list.
                                m_selectionWithControlKey = true;
                                m_selectedNodes.AddRange (m_currentSelectedCanvasNodes);
                                if (!m_selectedNodes.Contains (node)) {
                                    m_selectedNodes.Add (node);
                                }
                            }
                            else if (e.shift) {
                                // If the shift key is down add all current selected nodes in the canvas
                                // and remove the current selected if it's in the list.
                                m_selectionWithShiftKey = true;
                                m_selectedNodes.AddRange (m_currentSelectedCanvasNodes);
                                if (m_selectedNodes.Contains (node)) {
                                    m_selectedNodes.Remove (node);
                                }
                            }
                            else {
                                // If no other control key is down, two different
                                // modes can be performed. If the current selected node
                                // is already selected, the user want to drag all current selected canvas nodes
                                // or if he don't drag the current selected node, he only want to select this node.
                                //
                                // If the current selected node is not on the canvas selected, the user want to
                                // select and maybe drag only the current selected node.
                                if (m_currentSelectedCanvasNodes.Contains (node)) {
                                    m_selectedNodes.AddRange (m_currentSelectedCanvasNodes);
                                }
                                else {
                                    m_selectedNodes.Add (node);
                                }
                            }

                            // set all drag start positions of the current selected nodes.
                            m_nodeDragStartPositions = new Vector2[m_selectedNodes.Count];
                            for (int j = 0; j < m_nodeDragStartPositions.Length; j++) {
                                m_nodeDragStartPositions[j] = m_selectedNodes[j].position;
                            }

                            // select nodes visually.
                            canvas.SetSelectedNodes (m_selectedNodes.ToArray ());

                            // start drag if currently more than one node is selected.
                            m_dragStarted = m_selectedNodes.Count > 0;

                            GUI.SetNextControlName ("");
                            GUI.FocusControl ("");

                            e.Use ();
                            break;
                        }
                        else if (e.IsMouseDown (MouseButton.Right)) {
                            // select node for the context selection.
                            canvas.SetSelectedNode (node);
                            graph.Repaint ();
                            return;
                        }
                    }
                }

                // If no node was selected, the user want to start a multi selection.
                // Or deselect all nodes.
                if (!nodeSelected && e.IsMouseDown (MouseButton.Left)) {
                    m_multiSelectionStarted = true;

                    if (e.control) {
                        m_selectionWithControlKey = true;
                    }
                    else if (e.shift) {
                        m_selectionWithShiftKey = true;
                    }

                    // remove hot controls
                    GUI.SetNextControlName ("");
                    GUI.FocusControl ("");
                    e.Use ();
                }
            }

            // handle node drag.
            if (m_dragStarted) {
                // drag starts only if the user moves the mouse a short distance, while the mouse is down.
                if (!m_dragPerformed && Vector2.Distance (m_selectionStartPosition, canvas.mousePosition) > 5) {
                    m_dragPerformed = true;
                }

                if (e.IsMouseDrag (MouseButton.Left) && m_dragPerformed) {
                    m_dragPerformed = true;
                    m_dragDelta = canvas.mousePosition - m_selectionStartPosition;

                    if (canvas.snapNodesOnGrid) {
                        m_dragDelta = canvas.ConvertToSnapPosition (m_dragDelta);
                    }

                    for (int i = 0; i < m_selectedNodes.Count; i++) {
                        m_selectedNodes[i].position = m_nodeDragStartPositions[i] + m_dragDelta;
                    }

                    e.Use ();
                }
            }

            // handle node multi selection.
            if (m_multiSelectionStarted) {
                Vector2 currentCanvasPosition = canvas.mousePosition;

                // rearrange the min and max position.
                float xMinCanvas = Mathf.Min (m_selectionStartPosition.x, currentCanvasPosition.x);
                float yMinCanvas = Mathf.Min (m_selectionStartPosition.y, currentCanvasPosition.y);
                float xMaxCanvas = Mathf.Max (m_selectionStartPosition.x, currentCanvasPosition.x);
                float yMaxCanvas = Mathf.Max (m_selectionStartPosition.y, currentCanvasPosition.y);

                // create the current selection rect in canvas space.
                m_multiSelectionCanvasRect = Rect.MinMaxRect (xMinCanvas, yMinCanvas, xMaxCanvas, yMaxCanvas);

                // create a selection rect in screen space (for rendering) i dont want to scale the border of this rect.
                Vector2 min = canvas.CanvasToScreenPoint (new Vector2 (xMinCanvas, yMinCanvas));
                Vector2 max = canvas.CanvasToScreenPoint (new Vector2 (xMaxCanvas, yMaxCanvas));
                m_multiSelectionScreenRect = Rect.MinMaxRect (min.x, min.y, max.x, max.y);


                m_selectedNodes.Clear ();

                // If the user starts with control key down.
                // He want to add all nodes in the rect.
                if (m_selectionWithControlKey) {
                    m_selectedNodes.AddRange (m_currentSelectedCanvasNodes);

                    for (int i = 0; i < canvas.nodes.Length; i++) {
                        Node node = canvas.nodes[i];
                        if (m_multiSelectionCanvasRect.Overlaps (node.rect)) {
                            if (!m_selectedNodes.Contains (node)) {
                                m_selectedNodes.Add (node);
                            }
                        }
                    }
                }

                // If the user starts with shift key down.
                // he want to remove all nodes in the rect.
                else if (m_selectionWithShiftKey) {
                    m_selectedNodes.AddRange (m_currentSelectedCanvasNodes);

                    for (int i = 0; i < canvas.nodes.Length; i++) {
                        Node node = canvas.nodes[i];
                        if (m_multiSelectionCanvasRect.Overlaps (node.rect)) {
                            if (m_selectedNodes.Contains (node)) {
                                m_selectedNodes.Remove (node);
                            }
                        }
                    }
                }

                // The user want only select nodes in the rect.
                else {
                    for (int i = 0; i < canvas.nodes.Length; i++) {
                        Node node = canvas.nodes[i];
                        if (m_multiSelectionCanvasRect.Overlaps (node.rect)) {
                            if (!m_selectedNodes.Contains (node)) {
                                m_selectedNodes.Add (node);
                            }
                        }
                    }
                }

                canvas.SetHoveredNodes (m_selectedNodes.ToArray ());

                if (e.IsMouseDrag (MouseButton.Left)) {
                    e.Use ();
                }
            }

            if (e.IsMouseUp (MouseButton.Left, true)) {
                // remove hot controls
//                GUI.SetNextControlName ("");
//                GUI.FocusControl ("");

                if (m_dragStarted) {
                    // Check if drag was performed.
                    // If drag was performed, create a record for all dragged nodes.
                    if (m_dragPerformed) {
                        Node[] finalNodesToDrag = m_selectedNodes.ToArray ();
                        Vector2[] finalNodeDragStartPositions = m_nodeDragStartPositions;
                        Vector2 finalDragDelta = m_dragDelta;

                        string undoName = finalNodesToDrag.Length > 1 ? "Move [" + finalNodesToDrag.Length + "] nodes" : "Move node [" + finalNodesToDrag[0].name + "]";

                        GraphUndo.Record (undoName,
                            () => {
                                for (int i = 0; i < finalNodesToDrag.Length; i++) {
                                    finalNodesToDrag[i].position = finalNodeDragStartPositions[i];
                                }
                            },
                            () => {
                                for (int i = 0; i < finalNodesToDrag.Length; i++) {
                                    finalNodesToDrag[i].position = finalNodeDragStartPositions[i] + finalDragDelta;
                                }
                            });

                        graph.controller.isDirty = true;
                        e.Use ();
                    }

                    // If no drag was performed, check if a node was selected.
                    // If no node was selected, clear the selected nodes.
                    // Else check if the current selected node was selected and select only this.
                    // Or select all current selected nodes.
                    else {
                        if (m_selectionWithControlKey || m_selectionWithShiftKey) {
                            canvas.SetSelectedNodes (m_selectedNodes.ToArray ());
                        }
                        else if (m_currentSelectedNode != null) {
                            bool nodeWasNodeAlreadySelected = m_currentSelectedCanvasNodes.Contains (m_currentSelectedNode);

                            if (nodeWasNodeAlreadySelected) {
                                canvas.SetSelectedNode (m_currentSelectedNode);
                            }
                            else {
                                canvas.SetSelectedNodes (m_selectedNodes.ToArray ());
                            }
                        }
                        else {
                            canvas.ClearSelectedNodes ();
                        }

                        e.Use ();
                    }
                }
                else if (m_multiSelectionStarted) {
                    // If no node is in the multi selection rect, the user clicked in the canvas and will deselect all current selected nodes.
                    // Otherwise select all nodes, that are in the selection rect.  
                    if (m_selectedNodes.Count > 0) {
                        canvas.SetSelectedNodes (m_selectedNodes.ToArray ());
                    }
                    else {
                        canvas.ClearSelectedNodes ();
                    }

                    e.Use ();
                }

                // Reset all fields.
                m_currentSelectedNode = null;
                m_dragStarted = false;
                m_dragPerformed = false;
                m_multiSelectionStarted = false;
                m_selectionWithControlKey = false;
                m_selectionWithShiftKey = false;
            }
        }


        static class Styles {

            public static readonly EditorColor multiSelectionBackgroundColor = new EditorColor (new Color32 (62, 125, 231, 50), new Color32 (62, 95, 150, 50));
            public static readonly EditorColor multiSelectionOutlineColor = new EditorColor (new Color32 (62, 125, 231, 200), new Color32 (62, 95, 150, 200));

        }

    }


}
#endif